package com.qingcloud.df.executor.log.impl;

import cn.hutool.core.io.FileUtil;
import com.qingcloud.df.executor.pojo.JobInstanceLog;
import com.qingcloud.df.executor.log.IComponentLogger;
import com.qingcloud.df.executor.utils.ComponentUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * 组件文件类型日志实现
 *
 * @author wangjinnan
 */
@Slf4j
public class ComponentFileLogger implements IComponentLogger {

    /**
     * 单例对象
     */
    private static volatile ComponentFileLogger instance = null;

    private ComponentFileLogger() {

    }

    /**
     * 获取单例对象
     */
    public static ComponentFileLogger getInstance() {
        if (Objects.isNull(instance)) {
            synchronized (ComponentFileLogger.class) {
                if (Objects.isNull(instance)) {
                    instance = new ComponentFileLogger();
                }
            }
        }
        return instance;
    }

    /**
     * 追加utf8内容
     *
     * @param instanceId 组件实例ID
     * @return 追加是否成功
     */
    @Override
    public Boolean appendUtf8Log(Integer jobId, Integer instanceId, String softwareName, String content, String env) {
        FileUtil.appendUtf8String(content, ComponentUtil.getTodayLogFile(jobId, instanceId, softwareName, env));
        return true;
    }

    /**
     * 从已经读取的日志里面向前读日志
     *
     * @param instanceId 组件实例ID
     * @param preStart   往前读从哪行开始
     * @param afterStart 往后读从哪行开始
     * @param size       每次多少行日志
     * @param day        哪天的日志
     * @return 日志数据
     */
    @Override
    public JobInstanceLog preLog(Integer jobId, Integer instanceId, String softwareName, Long preStart, Long afterStart,
                                 Integer size, String day, String env) {
        //往前读，下次读取位置通过行数计算
        final JobInstanceLog fileLog = new JobInstanceLog();
        if (preStart < 1) {
            fileLog.setLogs(Collections.emptyList());
            fileLog.setAfterStart(String.valueOf(afterStart));
            fileLog.setPreStart("0");
            return fileLog;
        }
        long readStart = preStart - size + 1;
        final List<String> logList = readLog(jobId, instanceId, softwareName, readStart, size, day, env);
        fileLog.setLogs(logList);
        fileLog.setAfterStart(String.valueOf(afterStart));
        fileLog.setPreStart(String.valueOf(readStart <= 1 ? 0 : (readStart - 1)));
        log.info("read log {} from {}, size is {}, next start read {}", instanceId, preStart, size, fileLog.getAfterStart());
        return fileLog;
    }

    /**
     * 从已经读取的日志里面向后读日志
     *
     * @param instanceId 组件实例ID
     * @param preStart   往前读从哪行开始
     * @param afterStart 往后读从哪行开始
     * @param size       每次多少行日志
     * @param day        哪天的日志
     * @return 日志数据
     */
    @Override
    public JobInstanceLog afterLog(Integer jobId, Integer instanceId, String softwareName, Long preStart, Long afterStart,
                                   Integer size, String day, String env) {
        //往后读，下次读取位置通过行数计算
        final List<String> logList = readLog(jobId, instanceId, softwareName, afterStart, size, day, env);
        final JobInstanceLog fileLog = new JobInstanceLog();
        fileLog.setLogs(logList);
        fileLog.setPreStart(String.valueOf(preStart));
        fileLog.setAfterStart(String.valueOf(afterStart + logList.size()));
        log.info("read log {} from {}, size is {}, next start read {}", instanceId, afterStart, size, fileLog.getAfterStart());
        return fileLog;
    }

    /**
     * 最新日志读取
     *
     * @param instanceId 实例ID
     * @param size       每次读多少行
     * @param day        读取哪天的日志
     * @return 文件内容
     */
    @Override
    public JobInstanceLog readNewestLog(Integer jobId, Integer instanceId, String softwareName, Integer size, String day, String env) {
        //读取今天的日志文件
        final String logFile = ComponentUtil.getLogFile(jobId, instanceId, softwareName, day, env);
        //获取文件行数
        long totalLine = countLine(logFile);
        //读取最后size行
        long start = totalLine - size + 1;
        final List<String> lineList = readNeedLine(logFile, start, size);
        final JobInstanceLog fileLog = new JobInstanceLog();
        fileLog.setPreStart(String.valueOf(start <= 1 ? 0 : (start - 1)));
        fileLog.setAfterStart(String.valueOf(totalLine + 1));
        fileLog.setLogs(lineList);
        return fileLog;
    }

    /**
     * 获取文件行数
     *
     * @param file 文件路径
     * @return 行数
     */
    private long countLine(String file) {
        try (Stream<String> lineStream = Files.lines(Paths.get(new File(file).getPath()))) {
            return lineStream.count();
        } catch (IOException e) {
            //ignore
        }
        return 0;
    }


    /**
     * 读取运行实例今天的日志文件
     *
     * @param instanceId 实例ID
     * @param start      开始行数，从1开始
     * @param size       每次读多少行
     * @param day        读取哪天的日志
     * @return 文件内容
     */
    private List<String> readLog(Integer jobId, Integer instanceId, String softwareName, Long start, Integer size, String day, String env) {
        //读取今天的日志文件
        final String logFile = ComponentUtil.getLogFile(jobId, instanceId, softwareName, day, env);
        return readNeedLine(logFile, start, size);
    }

    /**
     * 读取文件需要的行
     *
     * @param file  文件路径
     * @param start 开始行数，从1开始
     * @param size  每次读多少行
     * @return 文件内容
     */
    private List<String> readNeedLine(String file, Long start, Integer size) {
        //检查日志文件是否存在
        if (!FileUtil.exist(file)) {
            return Collections.emptyList();
        }
        List<String> logList = new ArrayList<>(size);
        //读取今天的日志文件
        try (LineNumberReader reader = new LineNumberReader(new FileReader(file))) {
            for (int i = 0; i < (start + size); i++) {
                String line = reader.readLine();
                //读到末尾了
                if (Objects.isNull(line)) {
                    break;
                }
                //读到的行数在指定范围内，就返回
                final int lineNumber = reader.getLineNumber();
                if (lineNumber >= start && lineNumber < (start + size)) {
                    logList.add(line);
                }
            }
        } catch (Exception e) {
            //ignore
        }
        return logList;
    }

}
